<template>
    <a-button
        class="headerButton"
        @click="downloadMd"
        >下载
    </a-button>
    <!-- 导航栏 -->
    <div class="markdown-steps">
        <!-- 下载 -->
        <ul>
            <li
                v-for="(item, index) in stepItems"
                :key="item.href"
                @click="handleStepClick(index)"
            >
                <div>
                    <span
                        class="step-icon"
                        :class="{ 'step-icon-active': selectedId === index }"
                    ></span>
                    <a
                        :href="item.href"
                        class="step-title"
                        :class="{ 'step-title-active': selectedId === index }"
                        >{{ item.title }}</a
                    >
                </div>
                <div
                    v-if="stepItems[stepItems.length - 1] !== item"
                    class="step-line"
                ></div>
            </li>
        </ul>
    </div>
    <!-- body -->
    <div
        class="markdown-body"
        v-html="bodyHtml"
    ></div>
</template>

<script setup lang="ts">
    import { ref, onMounted, onUnmounted } from 'vue'
    import MarkdownIt from 'markdown-it'
    import hljs from 'highlight.js'
    import 'highlight.js/styles/github-dark.min.css'
    // @ts-ignore
    import 'github-markdown-css/github-markdown-light.css'
    import { message } from 'ant-design-vue'
    import markdownItAnchor from 'markdown-it-anchor'
    import markdownItTocDoneRight from 'markdown-it-toc-done-right'

    const props = defineProps<{ content: string }>()

    const stepItems = ref<{ href: string; title: string }[]>([])
    const bodyHtml = ref('')

    const md = new MarkdownIt({
        html: false, // 禁用内联 HTML（推荐）
        linkify: true, // 安全链接化
        breaks: true,
        highlight: (code, lang) => {
            // 代码高亮处理
            if (lang && hljs.getLanguage(lang)) {
                try {
                    return hljs.highlight(code, { language: lang }).value
                } catch (_) {}
            }
            return ''
        },
    })

    // 安全链接插件
    const secureLinkPlugin = (md: any) => {
        md.renderer.rules.link_open = (tokens: any, idx: any) => {
            const href = tokens[idx].attrGet('href')
            if (href.startsWith('javascript:')) {
                tokens[idx].attrSet('href', '#') // 拦截危险协议
            }
            return '<a target="_blank" rel="noopener noreferrer">'
        }
    }

    md.use(markdownItAnchor, {
        permalink: markdownItAnchor.permalink.linkInsideHeader({
            symbol: '',
            placement: 'after',
            class: 'header-anchor',
        }),
    })
        .use(markdownItTocDoneRight, {
            containerClass: 'toc-container', // TOC 容器的 CSS 类名
            // includeLevel: [1, 2, 3], // 包含 h1~h3 级标题
        })
        .use(secureLinkPlugin)

    const extractToc = (html: string) => {
        // 用 DOM 解析更安全
        const div = document.createElement('div')
        div.innerHTML = html
        const toc = div.querySelector('.toc-container')
        const tocContent = toc ? toc.outerHTML : ''
        if (toc) toc.remove()
        stepItems.value = extractStepsFromMarkdown(tocContent)
        // 处理 Typora blockquote 警告框
        const patched = patchTyporaBlockquotes(div.innerHTML)
        return { bodyHtml: patched }
    }

    // 处理 Typora 的 blockquote 警告框
    const patchTyporaBlockquotes = (html: string) => {
        const div = document.createElement('div')
        div.innerHTML = html

        div.querySelectorAll('blockquote').forEach((block) => {
            const firstP = block.querySelector('p')
            if (!firstP) return

            const match = firstP.textContent?.match(/^\[\!(\w+)\]$/)
            if (match) {
                const type = match[1].toLowerCase()
                block.classList.add(
                    'markdown-alert-define',
                    `markdown-alert-define-${type}`
                )

                // 根据类型生成对应的图标和文本
                let iconSvg = ''
                let displayText = ''

                switch (type) {
                    case 'note':
                        iconSvg =
                            '<svg style="width:16px;height:16px;color:#0969da;margin-right:4px;" viewBox="64 64 896 896" fill="currentColor"><path d="M 512 64 C 264.6 64 64 264.6 64 512 s 200.6 448 448 448 s 448 -200.6 448 -448 S 759.4 64 512 64 Z m 0 820 c -205.4 0 -372 -166.6 -372 -372 s 166.6 -372 372 -372 s 372 166.6 372 372 s -166.6 372 -372 372 Z"/><path d="M 464 336 a 48 48 0 1 0 96 0 a 48 48 0 1 0 -96 0 Z m 72 112 h -48 c -4.4 0 -8 3.6 -8 8 v 272 c 0 4.4 3.6 8 8 8 h 48 c 4.4 0 8 -3.6 8 -8 V 456 c 0 -4.4 -3.6 -8 -8 -8 Z"/></svg>'
                        displayText = 'Note'
                        break
                    case 'tip':
                        iconSvg =
                            '<svg style="width:16px;height:16px;color:#1f883d;margin-right:4px;" viewBox="64 64 896 896" fill="currentColor"><path d="M 632 888 H 392 c -4.4 0 -8 3.6 -8 8 v 32 c 0 17.7 14.3 32 32 32 h 192 c 17.7 0 32 -14.3 32 -32 v -32 c 0 -4.4 -3.6 -8 -8 -8 Z M 512 64 c -181.1 0 -328 146.9 -328 328 c 0 121.4 66 227.4 164 284.1 V 792 c 0 17.7 14.3 32 32 32 h 264 c 17.7 0 32 -14.3 32 -32 V 676.1 c 98 -56.7 164 -162.7 164 -284.1 c 0 -181.1 -146.9 -328 -328 -328 Z m 127.9 549.8 L 604 634.6 V 752 H 420 V 634.6 l -35.9 -20.8 C 305.4 568.3 256 484.5 256 392 c 0 -141.4 114.6 -256 256 -256 s 256 114.6 256 256 c 0 92.5 -49.4 176.3 -128.1 221.8 Z"/></svg>'
                        displayText = 'Tip'
                        break
                    case 'warning':
                        iconSvg =
                            '<svg style="width:16px;height:16px;color:#9a6700;margin-right:4px;" viewBox="64 64 896 896" fill="currentColor"><path d="M 464 720 a 48 48 0 1 0 96 0 a 48 48 0 1 0 -96 0 Z m 16 -304 v 184 c 0 4.4 3.6 8 8 8 h 48 c 4.4 0 8 -3.6 8 -8 V 416 c 0 -4.4 -3.6 -8 -8 -8 h -48 c -4.4 0 -8 3.6 -8 8 Z m 475.7 440 l -416 -720 c -6.2 -10.7 -16.9 -16 -27.7 -16 s -21.6 5.3 -27.7 16 l -416 720 C 56 877.4 71.4 904 96 904 h 832 c 24.6 0 40 -26.6 27.7 -48 Z m -783.5 -27.9 L 512 239.9 l 339.8 588.2 H 172.2 Z"/></svg>'
                        displayText = 'Warning'
                        break
                    case 'important':
                        iconSvg =
                            '<svg style="width:16px;height:16px;color:#8250df;margin-right:4px;" viewBox="64 64 896 896" fill="currentColor"><path d="M 625.9 115 c -5.9 0 -11.9 1.6 -17.4 5.3 L 254 352 H 90 c -8.8 0 -16 7.2 -16 16 v 288 c 0 8.8 7.2 16 16 16 h 164 l 354.5 231.7 c 5.5 3.6 11.6 5.3 17.4 5.3 c 16.7 0 32.1 -13.3 32.1 -32.1 V 147.1 c 0 -18.8 -15.4 -32.1 -32.1 -32.1 Z M 586 803 L 293.4 611.7 l -18 -11.7 H 146 V 424 h 129.4 l 17.9 -11.7 L 586 221 v 582 Z m 348 -327 H 806 c -8.8 0 -16 7.2 -16 16 v 40 c 0 8.8 7.2 16 16 16 h 128 c 8.8 0 16 -7.2 16 -16 v -40 c 0 -8.8 -7.2 -16 -16 -16 Z m -41.9 261.8 l -110.3 -63.7 a 15.9 15.9 0 0 0 -21.7 5.9 l -19.9 34.5 c -4.4 7.6 -1.8 17.4 5.8 21.8 L 856.3 800 a 15.9 15.9 0 0 0 21.7 -5.9 l 19.9 -34.5 c 4.4 -7.6 1.7 -17.4 -5.8 -21.8 Z M 760 344 a 15.9 15.9 0 0 0 21.7 5.9 L 892 286.2 c 7.6 -4.4 10.2 -14.2 5.8 -21.8 L 878 230 a 15.9 15.9 0 0 0 -21.7 -5.9 L 746 287.8 a 15.99 15.99 0 0 0 -5.8 21.8 L 760 344 Z"/></svg>'
                        displayText = 'Important'
                        break
                    case 'caution':
                        iconSvg =
                            '<svg style="width:16px;height:16px;color:#cf222e;margin-right:4px;" viewBox="64 64 896 896" fill="currentColor"><path d="M 512 64 C 264.6 64 64 264.6 64 512 s 200.6 448 448 448 s 448 -200.6 448 -448 S 759.4 64 512 64 Z m 0 820 c -205.4 0 -372 -166.6 -372 -372 s 166.6 -372 372 -372 s 372 166.6 372 372 s -166.6 372 -372 372 Z"/><path d="M 464 688 a 48 48 0 1 0 96 0 a 48 48 0 1 0 -96 0 Z m 24 -112 h 48 c 4.4 0 8 -3.6 8 -8 V 296 c 0 -4.4 -3.6 -8 -8 -8 h -48 c -4.4 0 -8 3.6 -8 8 v 272 c 0 4.4 3.6 8 8 8 Z"/></svg>'
                        displayText = 'Caution'
                        break
                    default:
                        iconSvg =
                            '<svg style="width:16px;height:16px;color:#0969da;margin-right:4px;" viewBox="64 64 896 896" fill="currentColor"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z"/></svg>'
                        displayText =
                            type.charAt(0).toUpperCase() + type.slice(1)
                }

                firstP.innerHTML = `<span style="display:inline-flex;align-items:center;">${iconSvg}${displayText}</span>`
            }
        })

        return div.innerHTML
    }

    const extractStepsFromMarkdown = (mdText: string) => {
        let match
        const result = []
        const regex = /<a\b[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/g
        while ((match = regex.exec(mdText)) !== null) {
            result.push({ href: match[1], title: match[2] })
        }
        return result
    }

    // 读取远程文件（需解决跨域）
    const loadRemoteMd = async () => {
        if (!props.content) return
        try {
            const response = await fetch(
                `${import.meta.env.VITE_APP_BASE_API}${props.content}`
            )
            const text = await response.text()

            // 渲染带 TOC 的 HTML
            const htmlWithToc = md.render('[TOC]\n' + text)

            const { bodyHtml: body } = extractToc(htmlWithToc)
            bodyHtml.value = body
        } catch (error) {
            message.error('加载失败')
        }
    }

    const selectedId = ref(0)
    const handleStepClick = (index: number) => {
        selectedId.value = index
    }

    // 监听锚点
    const observer = new IntersectionObserver(
        (entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    const activeId = entry.target.id
                    // 根据锚点ID找到对应的step索引
                    const stepIndex = stepItems.value.findIndex(
                        (item) => item.href === `#${activeId}`
                    )

                    if (stepIndex !== -1) {
                        selectedId.value = stepIndex
                    }
                }
            })
        },
        {
            threshold: 0.5, // 元素50%进入视口时触发
            rootMargin: '0px 0px -60% 0px', // 调整检测区域，更精确地检测视口中部
        }
    )

    // 观察所有标题元素
    const observeHeadings = () => {
        const markdownBody = document.querySelector('.markdown-body')
        if (!markdownBody) return

        const headings = markdownBody.querySelectorAll('h1, h2, h3')

        headings.forEach((heading) => {
            observer.observe(heading)
        })
    }

    onMounted(() => {
        loadRemoteMd().then(() => {
            // 等待DOM渲染完成后观察标题
            setTimeout(() => {
                observeHeadings()
                // 设置初始选中项
                if (stepItems.value.length > 0) {
                    selectedId.value = 0
                }
            }, 200)
        })
    })

    // 组件卸载时清理observer
    onUnmounted(() => {
        observer.disconnect()
    })

    // 下载
    const downloadMd = async () => {
        if (!props.content) {
            message.error('没有可下载的文件')
            return
        }

        try {
            // 获取文件内容
            const response = await fetch(
                `${import.meta.env.VITE_APP_BASE_API}${props.content}`
            )

            if (!response.ok) {
                message.error('文件下载失败')
                return
            }

            const blob = await response.blob()

            // 从路径中提取文件名
            const filename = props.content.split('/').pop() || 'document.md'

            // 创建下载链接
            const url = window.URL.createObjectURL(blob)
            const link = document.createElement('a')
            link.href = url
            link.download = filename

            // 触发下载
            document.body.appendChild(link)
            link.click()

            // 清理
            document.body.removeChild(link)
            window.URL.revokeObjectURL(url)

            message.success('文件下载成功')
        } catch (error) {
            console.error('下载失败:', error)
            message.error('文件下载失败')
        }
    }
</script>

<style scoped lang="less">
    .headerButton {
        position: absolute;
        z-index: 1000;
        left: 1145px;
        top: 140px;
    }
    .markdown-steps {
        position: fixed;
        top: 80px;
        width: 200px;
        right: 60px;
        z-index: 100;
        overflow-y: scroll;
        max-height: calc(100vh - 120px);
        .step-icon {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            display: inline-block;
            background-color: rgba(0, 0, 0, 0.3);
            margin-right: 10px;
        }
        .step-line {
            width: 1px;
            height: 28px;
            position: relative;
            left: 5px;
            background-color: rgba(0, 0, 0, 0.3);
        }
        .step-title {
            color: rgba(0, 0, 0, 0.7);
            font-size: 14px;
            transition:
                background 0.2s,
                color 0.2s;
        }
        .step-title-active {
            color: var(--color-primary);
        }
        .step-icon-active {
            background-color: var(--color-primary);
        }
    }

    .markdown-steps::-webkit-scrollbar {
        width: 0;
    }

    .markdown-body {
        padding: 20px;
        border-radius: 10px;
        width: 950px;
        position: relative;
        left: 0;
    }
</style>

<style lang="less">
    .markdown-alert-define {
        padding: 10px !important;
        margin: 10px 0 !important;
        border-radius: 2px !important;
    }
    .markdown-alert-define-tip {
        border-left: 0.25rem solid rgb(31, 136, 61) !important;
        background-color: #dafbe1 !important;
    }
    .markdown-alert-define-note {
        border-left: 0.25rem solid rgb(9, 105, 218) !important;
        background-color: #f6f8fa !important;
    }
    .markdown-alert-define-important {
        border-left: 0.25rem solid rgb(130, 80, 223) !important;
        background-color: #faf0ff !important;
    }
    .markdown-alert-define-warning {
        border-left: 0.25rem solid rgb(154, 103, 0) !important;
        background-color: #fff8c5 !important;
    }
    .markdown-alert-define-caution {
        border-left: 0.25rem solid rgb(207, 34, 46) !important;
        background-color: #ffebe9 !important;
    }
</style>
